﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

// ReSharper disable CheckNamespace
namespace MugenMvvmToolkit
// ReSharper restore CheckNamespace
{
    internal static class ReflectionExtensions
    {
        #region Fields

        private static readonly Dictionary<Type, PropertyInfo[]> Properties = new Dictionary<Type, PropertyInfo[]>(30);

        #endregion

        #region Methods

        /// <summary>
        ///     Gets the types defined in this assembly.
        /// </summary>
        /// <param name="assembly">
        ///     The specified <see cref="Assembly" />.
        /// </param>
        /// <returns>
        ///     An array of type <see cref="T:System.Type" /> containing objects for all the types defined in this assembly.
        /// </returns>
        public static IEnumerable<Type> GetTypes(this Assembly assembly)
        {
            return assembly.DefinedTypes.Select(info => info.AsType());
        }

        /// <summary>
        ///     Determines whether the specified object is an instance of the current <see cref="T:System.Type" />.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="instance"></param>
        /// <returns></returns>
        public static bool IsInstanceOfType(this Type type, object instance)
        {
            if (instance == null)
                return false;
            return type.IsAssignableFrom(instance.GetType());
        }

        /// <summary>
        ///     Determines whether an instance of the current <see cref="T:System.Type" /> can be assigned from an instance of the specified Type.
        /// </summary>
        /// <param name="type">
        ///     The specified <see cref="Type" />.
        /// </param>
        /// <param name="c">The Type to compare with the current Type.</param>
        /// <returns>
        ///     true if <paramref name="c" /> and the current Type represent the same type, or if the current Type is in the inheritance hierarchy of
        ///     <paramref
        ///         name="c" />
        ///     , or if the current Type is an interface that <paramref name="c" /> implements, or if
        ///     <paramref
        ///         name="c" />
        ///     is a generic type parameter and the current Type represents one of the constraints of
        ///     <paramref
        ///         name="c" />
        ///     . false if none of these conditions are true, or if <paramref name="c" /> is null.
        /// </returns>
        public static bool IsAssignableFrom(this Type type, Type c)
        {
            if (c == null)
                return false;
            return type.GetTypeInfo().IsAssignableFrom(c.GetTypeInfo());
        }

        /// <summary>
        ///     Indicates whether one or more attributes of the specified type or of its derived types is applied to this member.
        /// </summary>
        /// <param name="type">
        ///     The specified <see cref="Type" />.
        /// </param>
        /// <param name="attributeType">The type of custom attribute to search for. The search includes derived types. </param>
        /// <param name="inherit">true to search this member's inheritance chain to find the attributes; otherwise, false. This parameter is ignored for properties and events; see Remarks.</param>
        /// <returns>
        ///     true if one or more instances of <paramref name="attributeType" /> or any of its derived types is applied to this member; otherwise, false.
        /// </returns>
        public static bool IsDefined(this Type type, Type attributeType, bool inherit)
        {
            return type.GetTypeInfo().IsDefined(attributeType, inherit);
        }

        /// <summary>
        /// Returns all the properties of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.PropertyInfo"/> objects representing all properties defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        public static PropertyInfo[] GetProperties(this Type type)
        {
            lock (Properties)
            {
                PropertyInfo[] properties;
                if (!Properties.TryGetValue(type, out properties))
                {
                    properties = type.GetPropertiesInternal().ToArray();
                    Properties[type] = properties;
                }
                return properties;
            }
        }

        /// <summary>
        /// Returns all the property of the current <see cref="T:System.Type"/>.
        /// </summary> 
        /// <returns>
        /// An array of <see cref="T:System.Reflection.PropertyInfo"/> objects representing all methods defined for the current <see cref="T:System.Type"/>.
        /// </returns>
        private static IEnumerable<PropertyInfo> GetPropertiesInternal(this Type type)
        {
            if (type == null)
                return new PropertyInfo[0];
            var result = new List<PropertyInfo>(type.GetTypeInfo().DeclaredProperties);
            foreach (var methodInfo in GetPropertiesInternal(type.GetTypeInfo().BaseType))
            {
                bool canAdd = true;
                foreach (var info in result)
                {
                    if (!info.Name.Equals(methodInfo.Name) || info.PropertyType != methodInfo.PropertyType) continue;
                    if (!info.Name.Equals("Item"))
                    {
                        canAdd = false;
                        break;
                    }
                    if (info.GetMethod != null && methodInfo.GetMethod != null)
                    {
                        if (IsMethodEquals(info.GetMethod, methodInfo.GetMethod))
                        {
                            canAdd = false;
                            break;
                        }
                    }
                    if (info.SetMethod != null && methodInfo.SetMethod != null)
                    {
                        if (IsMethodEquals(info.SetMethod, methodInfo.SetMethod))
                        {
                            canAdd = false;
                            break;
                        }
                    }
                }
                if (!canAdd) continue;
                result.Add(methodInfo);
            }
            return result;
        }

        private static bool IsMethodEquals(MethodInfo methodInfo, MethodInfo info)
        {
            if (!info.Name.Equals(methodInfo.Name) || info.ReturnType != methodInfo.ReturnType) return false;
            var parameterInfos = methodInfo.GetParameters();
            var parameters = info.GetParameters();
            if (parameterInfos.Length != parameters.Length) return false;
            bool parameterEqual = true;
            for (int i = 0; i < parameterInfos.Length; i++)
            {
                if (parameterInfos[i].ParameterType != parameters[i].ParameterType)
                {
                    parameterEqual = false;
                    break;
                }
            }
            return parameterEqual;
        }

        #endregion

    }
}